const CPP = require("./tree-sitter-cpp/grammar.js")

module.exports = grammar(CPP, {
  name: 'cpp',

  rules: {

    _top_level_item: ($, original) => choice(
      $.alone_macro,
      $.alone_macro_call,
      original,
    ),

    _field_declaration_list_item: ($, original) => choice(
      original,
      $.macro_statement,
    ),

    alone_macro: $ => /[_A-Z][_A-Z0-9]+\s*\n/,
    alone_macro_call: $ => seq(
      /[_A-Z][_A-Z0-9]+/,
      '(',
      optional(seq(/[_A-Z][_A-Z0-9]+/, repeat(seq(',', /[_A-Z][_A-Z0-9]+/)))),
      ')',
      '\n',
    ),

    class_specifier: $ => prec.right(seq(
      'class',
      repeat($.macro_annotation),
      choice(
        field('name', $._class_name),
        seq(
          optional(field('name', $._class_name)),
          optional($.virtual_specifier),
          optional($.base_class_clause),
          field('body', $.field_declaration_list)
        )
      )
    )),

    struct_specifier: $ => prec.right(seq(
      'struct',
      optional($.macro_annotation),
      choice(
        field('name', $._class_name),
        seq(
          optional(field('name', $._class_name)),
          optional($.virtual_specifier),
          optional($.base_class_clause),
          field('body', $.field_declaration_list)
        )
      )
    )),

    parameter_list: ($, original) => seq(
      original,
      optional($.macro_annotation),
    ),

    storage_class_specifier: ($, original) => choice(
      original,
      $.macro_annotation,
    ),

    call_expression: ($, original) => choice(
      original,
      $._call_macro_with_decl_first_arg,
    ),

    _call_macro_with_decl_first_arg: $ => seq(
      field('function', choice(
        'CACHE_TRY_INSPECT',
        'CACHE_TRY_UNWRAP',
        'FORWARD',
        'FORWARD_SET_ATTRIBUTE',
        'IDB_TRY_INSPECT',
        'IDB_TRY_UNWRAP',
        'LS_TRY_INSPECT',
        'LS_TRY_UNWRAP',
        'SDB_TRY_INSPECT',
        'SDB_TRY_UNWRAP',
        'PS_GET',
        'PS_GET_AND_SET',
        'PS_GET_LOCKLESS',
        'QM_TRY_INSPECT',
        'QM_NOTEONLY_TRY_UNWRAP',
        'QM_TRY_UNWRAP',
        'QM_WARNONLY_TRY_UNWRAP',
      )),
      field('arguments', seq(
        '(',
        $.parameter_declaration,
        ',',
        commaSep(choice($._expression, $.initializer_list)),
        ')',
      )),
    ),

    macro_statement: $ => choice(
      'MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER'
    ),
    
    macro_annotation: $ => choice(
      'MOZ_ALLOCATOR',
      'MOZ_ALLOW_TEMPORARY',
      'MOZ_ALWAYS_INLINE',
      'MOZ_ALWAYS_INLINE_EVEN_DEBUG',
      'MOZ_ASAN_BLACKLIST',
      'MOZ_CAN_RUN_SCRIPT',
      'MOZ_CAN_RUN_SCRIPT_BOUNDARY',
      'MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION',
      'MOZ_COLD',
      'MOZ_FALLTHROUGH',
      'MOZ_FORMAT_PRINTF',
      'MOZ_HAVE_ANALYZER_NORETURN',
      'MOZ_HAVE_ASAN_BLACKLIST',
      'MOZ_HAVE_NEVER_INLINE',
      'MOZ_HAVE_NORETURN',
      'MOZ_HAVE_NORETURN_PTR',
      'MOZ_HAVE_NO_SANITIZE_ATTR',
      'MOZ_HAVE_SIGNED_OVERFLOW_SANITIZE_ATTR',
      'MOZ_HAVE_UNSIGNED_OVERFLOW_SANITIZE_ATTR',
      'MOZ_HEAP_ALLOCATOR',
      'MOZ_HEAP_CLASS',
      'MOZ_IMPLICIT',
      'MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS',
      'MOZ_INIT_OUTSIDE_CTOR',
      'MOZ_IS_CLASS_INIT',
      'MOZ_IS_REFPTR',
      'MOZ_IS_SMARTPTR_TO_REFCOUNTED',
      'MOZ_MAYBE_UNUSED',
      'MOZ_MAY_CALL_AFTER_MUST_RETURN',
      'MOZ_MUST_OVERRIDE',
      'MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG',
      'MOZ_MUST_USE',
      'MOZ_MUST_USE_TYPE',
      'MOZ_NEEDS_MEMMOVABLE_MEMBERS',
      'MOZ_NEEDS_MEMMOVABLE_TYPE',
      'MOZ_NEEDS_NO_VTABLE_TYPE',
      'MOZ_NEVER_INLINE',
      'MOZ_NEVER_INLINE_DEBUG',
      'MOZ_NONHEAP_CLASS',
      'MOZ_NONNULL',
      'MOZ_NONNULL_RETURN',
      'MOZ_NON_AUTOABLE',
      'MOZ_NON_MEMMOVABLE',
      'MOZ_NON_OWNING_REF',
      'MOZ_NON_PARAM',
      'MOZ_NON_TEMPORARY_CLASS',
      'MOZ_NORETURN',
      'MOZ_NORETURN_PTR',
      'MOZ_NO_ADDREF_RELEASE_ON_RETURN',
      'MOZ_NO_ARITHMETIC_EXPR_IN_ARGUMENT',
      'MOZ_NO_DANGLING_ON_TEMPORARIES',
      'MOZ_NO_SANITIZE_SIGNED_OVERFLOW',
      'MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW',
      'MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS',
      'MOZ_OWNING_REF',
      'MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS',
      'MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS',
      'MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS',
      'MOZ_RAII',
      'MOZ_REQUIRED_BASE_METHOD',
      'MOZ_STACK_CLASS',
      'MOZ_STATIC_CLASS',
      'MOZ_STATIC_LOCAL_CLASS',
      'MOZ_TEMPORARY_CLASS',
      'MOZ_TRIVIAL_CTOR_DTOR',
      'MOZ_TSAN_BLACKLIST',
      'MOZ_UNSAFE_REF',
      'MOZ_XPCOM_ABI',
      'JS_PUBLIC_API',
    ),

    primitive_type: $ => token(choice(
      'APIENTRY',
      'ATOM',
      'BOOL',
      'BOOLEAN',
      'BYTE',
      'CCHAR',
      'CHAR',
      'COLORREF',
      'DWORD',
      'DWORDLONG',
      'DWORD_PTR',
      'DWORD32',
      'DWORD64',
      'FLOAT',
      'HACCEL',
      'HALF_PTR',
      'HANDLE',
      'HBITMAP',
      'HBRUSH',
      'HCOLORSPACE',
      'HCONV',
      'HCONVLIST',
      'HCURSOR',
      'HDC',
      'HDDEDATA',
      'HDESK',
      'HDROP',
      'HDWP',
      'HENHMETAFILE',
      'HFILE',
      'HFONT',
      'HGDIOBJ',
      'HGLOBAL',
      'HHOOK',
      'HICON',
      'HINSTANCE',
      'HKEY',
      'HKL',
      'HLOCAL',
      'HMENU',
      'HMETAFILE',
      'HMODULE',
      'HMONITOR',
      'HPALETTE',
      'HPEN',
      'HRESULT',
      'HRGN',
      'HRSRC',
      'HSZ',
      'HWINSTA',
      'HWND',
      'INT',
      'INT_PTR',
      'INT8',
      'INT16',
      'INT32',
      'INT64',
      'LANGID',
      'LCID',
      'LCTYPE',
      'LGRPID',
      'LONG',
      'LONGLONG',
      'LONG_PTR',
      'LONG32',
      'LONG64',
      'LPARAM',
      'LPBOOL',
      'LPBYTE',
      'LPCOLORREF',
      'LPCSTR',
      'LPCVOID',
      'LPCWSTR',
      'LPDWORD',
      'LPHANDLE',
      'LPINT',
      'LPLONG',
      'LPSTR',
      'LPTSTR',
      'LPWOID',
      'LPWORD',
      'LPWSTR',
      'LRESULT',
      'PBOOL',
      'PBOOLEAN',
      'PBYTE',
      'PCHAR',
      'PCSTR',
      'PCTSTR',
      'PCWSTR',
      'PDWORD',
      'PDWORDLONG',
      'PDWORD_PTR',
      'PDWORD32',
      'PDWORD64',
      'PFLOAT',
      'PHALF_PTR',
      'PHANDLE',
      'PHKEY',
      'PINT',
      'PINT_PTR',
      'PINT8',
      'PINT16',
      'PINT32',
      'PINT64',
      'PLCID',
      'PLONG',
      'PLONGLONG',
      'PLONG32',
      'PLONG64',
      'POINTER_32',
      'POINTER_64',
      'POINTER_SIGNED',
      'POINTER_UNSIGNED',
      'PSHORT',
      'PSIZE_T',
      'PSSIZE_T',
      'PSTR',
      'PTBYTE',
      'PTCHAR',
      'PTSTR',
      'PUCHAR',
      'PUHALF_PTR',
      'PUINT',
      'PUINT_PTR',
      'PUINT8',
      'PUINT16',
      'PUINT32',
      'PUINT64',
      'PULONG',
      'PULONGLONG',
      'PULONG32',
      'PULONG64',
      'PUSHORT',
      'PVOID',
      'PWCHAR',
      'PWORD',
      'PWSTR',
      'QWORD',
      'SC_HANDLE',
      'SC_LOCK',
      'SERVICE_STATUS_HANDLE',
      'SHORT',
      'SIZE_T',
      'SSIZE_T',
      'TBYTE',
      'TCHAR',
      'UCHAR',
      'UHALF_PTR',
      'UINT',
      'UINT_PTR',
      'UINT8',
      'UINT16',
      'UINT32',
      'UINT64',
      'ULONG',
      'ULONGLONG',
      'ULONG_PTR',
      'ULONG32',
      'ULONG64',
      'UNICODE_STRING',
      'USHORT',
      'USN',
      'VOID',
      'WCHAR',
      'WORD',
      'WPARAM',
      'bool',
      'char',
      'int',
      'float',
      'double',
      'void',
      'size_t',
      'ssize_t',
      'intptr_t',
      'uintptr_t',
      'charptr_t',
      'intmax_t',
      'intptr_t',
      'uintmax_t',
      'uintptr_t',
      'ptrdiff_t',
      'max_align_t',
      'wchar_t',
      'sig_atomic_t',
      ...[8, 16, 32, 64].map(n => `int${n}_t`),
      ...[8, 16, 32, 64].map(n => `uint${n}_t`),
      ...[8, 16, 32, 64].map(n => `char${n}_t`),
      ...[8, 16, 32, 64].map(n => `int_fast${n}_t`),
      ...[8, 16, 32, 64].map(n => `int_least${n}_t`),
      ...[8, 16, 32, 64].map(n => `uint_fast${n}_t`),
      ...[8, 16, 32, 64].map(n => `uint_least${n}_t`),
    )),

    concatenated_string: $ => seq(
      choice($.raw_string_literal, $.string_literal),
      repeat1(choice($.raw_string_literal, $.string_literal, $.identifier))
    ),
  }
});

function commaSep(rule) {
  return optional(commaSep1(rule));
}

function commaSep1(rule) {
  return seq(rule, repeat(seq(',', rule)));
}
